package edu.dhbw.mannheim;

import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.users.User;

@SuppressWarnings("serial")
public class Admin extends HttpServlet {
	/**
	 * Get a reference to the admin key in the datastore
	 */
	private static Key adminKey = KeyFactory.createKey("admin", "blogblogblog");
	private Pattern pattern;
	private Matcher matcher;

//	private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@"
//			+ "[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
	private static final String USER_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*(@"
			+ "[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,}))?$";

	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {

		// check if user is admin
		if (!Admin.isAdmin(req.getRemoteUser())) {
			return;
		}

		// Get the POST type
		String type = req.getParameter("type");
		if (type == null)
			return;

		// ############################################################
		// ################## delete admin ############################
		// ############################################################
		if (type.equals("deleteAdmin")) {
			resp.setStatus(400); // default for return status
			String username = req.getParameter("username");
			if (username == null)
				return;

			User userToDel = findUser(createUserObject(username).getEmail());
			if (userToDel != null) {
				dropAdmin(userToDel);

				// return the ID
				try {
					PrintWriter out = resp.getWriter();
					out.print(username);
				} catch (IOException e) {
					e.printStackTrace();
				}
				resp.setStatus(200);
			}
		}
		// ############################################################
		// ################## create new admin ########################
		// ############################################################
		else if (type.equals("newAdmin")) {
			String username = req.getParameter("username");
			if (username == null)
				return;
			
			if (!validateUser(username)) {
				System.out
						.println("Username contains characters, that are not allowed!");
				return;
			}

			// check that username is a valid email address
			// 1. is not empty
			if (username.isEmpty()) {
				// username is empty
				System.err.println("The username is empty");
				return;
			}
			// 2. does not exist
			if (findUser(createUserObject(username).getEmail()) != null) {
				// user exists
				System.err.println("The user is already in database");
				return;
			}

			// collect information from request
			int canPost = 0;
			int isAdmin = 0;
			if (req.getParameter("canPost") != null
					&& req.getParameter("canPost").equals("true"))
				canPost = 1;
			if (req.getParameter("isAdmin") != null
					&& req.getParameter("isAdmin").equals("true"))
				isAdmin = 1;

			// add new admin to datastore
			Entity na = addAdmin(createUserObject(username), canPost, isAdmin);
			if (na == null)
				return;

			// return the ID
			try {
				PrintWriter out = resp.getWriter();
				out.print(createNewAdminRow(createUserObject(username),
						canPost, isAdmin, (Date) na.getProperty("date")));
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}
		}
		// ############################################################
		// ################## modify admin ############################
		// ############################################################
		else if (type.equals("modifyAdmin")) {
			String username;
			int canPost;
			int isAdmin;
			try {
				username = req.getParameter("username");
				canPost = Integer.parseInt(req.getParameter("canPost"));
				isAdmin = Integer.parseInt(req.getParameter("isAdmin"));
			} catch (Exception e) {
				System.out
						.println("Error reading request data for modifying admin: "
								+ e.getMessage());
				return;
			}
			modifyAdmin(createUserObject(username), canPost, isAdmin);
		}
	}

	/**
	 * @brief Construct a admin row with the given data and return it as a
	 *        string
	 * @param user
	 *            User object of new admin
	 * @param canPost
	 *            0 or 1
	 * @param isAdmin
	 *            0 or 1
	 * @param date
	 *            Date when admin was added
	 * @return string as html content
	 */
	public static String createNewAdminRow(User user, int canPost, int isAdmin,
			Date date) {
		String result = "";
		result += "<td>" + user.getEmail() + "</td>\n";
		result += "<td><input type='checkbox' onClick='modifyAdmin(this)' "
				+ (canPost == 1 ? "checked" : "") + " /></td>";
		result += "<td><input type='checkbox' onClick='modifyAdmin(this)' "
				+ (isAdmin == 1 ? "checked" : "") + " /></td>";
		result += "<td>" + Admin.getFormatedDate(date) + "</td>";
		result += "<td><a href=\"#\" onClick=\"javascript:deleteAdmin(this)\"><b>x</b></a></td>"; // '"+username+"'
		return result;
	}

	/**
	 * @brief Check if given user has the admin role
	 * @param user
	 *            User to check
	 * @return true if user is admin
	 */
	public static boolean isAdmin(String user) {
		Entity admin = getAdminEntity(createUserObject(user));
		if (admin != null && (Long) admin.getProperty("admin") == 1)
			return true;
		return false;
	}

	/**
	 * @brief Create a User object from given username
	 * @param username
	 *            If the username is an email address, the domain is used for
	 *            the user object
	 * @return User object
	 */
	private static User createUserObject(String username) {
		String[] userParts = username.split("@");
		String domain = "gmail.com";
		if (userParts.length == 2) {
			domain = userParts[1];
			username = userParts[0];
		}
		String email = username + "@" + domain;
		User user = new User(email, domain);
		return user;

	}

	/**
	 * @brief Check if given user has permission to post
	 * @param user
	 *            User to check
	 * @return true if user is a blogger
	 */
	public static boolean canPost(String user) {
		Entity admin = getAdminEntity(createUserObject(user));
		if (admin != null && (Long) admin.getProperty("post") == 1)
			return true;
		return false;
	}

	/**
	 * This method is quite the some as the method below. It would be faster,
	 * but it does not add admins, if there are not any...
	 */
	// private static Entity getAdminEntity(String username) {
	// DatastoreService datastore = DatastoreServiceFactory
	// .getDatastoreService();
	// Query query = new Query("admin", adminKey).addFilter("user",
	// Query.FilterOperator.EQUAL, username);
	// Entity user = datastore.prepare(query).asSingleEntity();
	// return user;
	// }

	/**
	 * @brief Get the admin entity of a given user
	 * @param user
	 *            User object
	 * @return admin entity
	 */
	private static Entity getAdminEntity(User user) {
		if (user == null)
			return null; // a null user is generally not an admin ;)
		List<Entity> users = getListOfAdminEntities();
		if (users.isEmpty()) {
			// if there are no admins, check if user has a known email address
			// and add him
			if (user.getEmail().equals("nicolai.ommer@gmail.com")
					|| user.getEmail().equals("klostermannn@googlemail.com")) {
				return addAdmin(user, 1, 1);
			}
			return null;
		} else {
			for (Entity selEntity : users) {
				String selUser = selEntity.getProperty("user").toString();
				if (selUser != null && user.getEmail().equals(selUser)) {
					return selEntity;
				}
			}
		}
		return null;
	}

	/**
	 * @brief Add an admin to the datastore
	 * @param user
	 * @param canPost
	 * @param isAdmin
	 * @return
	 */
	public static Entity addAdmin(User user, int canPost, int isAdmin) {
		Date date = new Date();
		Entity admin = new Entity("admin", adminKey);
		admin.setProperty("user", user.getEmail());
		admin.setProperty("date", date);
		admin.setProperty("admin", new Long(isAdmin)); // permission to give
														// permission
		admin.setProperty("post", new Long(canPost)); // permission to post

		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		datastore.put(admin);
		return admin;
	}

	/**
	 * @brief Change properties in datastore for given user
	 * @param user
	 * @param canPost
	 * @param isAdmin
	 */
	public static void modifyAdmin(User user, int canPost, int isAdmin) {
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		Query query = new Query("admin", adminKey).setKeysOnly().addFilter(
				"user", Query.FilterOperator.EQUAL, user.getEmail());
		List<Entity> admins = datastore.prepare(query).asList(
				FetchOptions.Builder.withDefaults());

		if (admins.isEmpty())
			System.out.println("There is no user " + user.getEmail()
					+ " to modify.");

		// in case, there are duplicate users
		for (Entity admin : admins) {
			admin.setProperty("user", user.getEmail());
			admin.setProperty("date", new Date());
			admin.setProperty("admin", new Long(isAdmin)); // permission to give
															// permission
			admin.setProperty("post", new Long(canPost)); // permission to post
			datastore.put(admin);
		}
	}

	/**
	 * @brief Ask the datastore for the complete list of admins
	 * @return list of all admins
	 */
	public static List<Entity> getListOfAdminEntities() {
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		Query queryUser = new Query("admin", adminKey);
		List<Entity> users = datastore.prepare(queryUser).asList(
				FetchOptions.Builder.withDefaults());
		return users;
	}

	/**
	 * @brief Delete given user from datastore
	 * @param user
	 */
	private static void dropAdmin(User user) {
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		Query query = new Query("admin", adminKey).setKeysOnly().addFilter(
				"user", Query.FilterOperator.EQUAL, user.getEmail());
		List<Entity> admins = datastore.prepare(query).asList(
				FetchOptions.Builder.withDefaults());

		// in case, there are duplicate users
		for (Entity admin : admins) {
			// delete the entry
			datastore.delete(admin.getKey());
		}
	}

	/**
	 * @brief find given username in datastore and return user object
	 * @param username
	 * @return
	 */
	public static User findUser(String username) {
		List<Entity> admins = getListOfAdminEntities();
		for (Entity admin : admins) {
			String userMail = (String) admin.getProperty("user");
			if (userMail != null && userMail.equals(username)) {
				return createUserObject(username);
			}
			String auth[] = userMail.split("@");
			String userWOMail = auth[0];
			String userDomain = auth.length==2?auth[1]:"";
			if(userDomain.equals("gmail.com") && userWOMail != null && userWOMail.equals(username)) {
				return createUserObject(username);
			}
		}
		return null;
	}

	/**
	 * @brief Get a formated date
	 * @param date
	 * @return
	 */
	public static String getFormatedDate(Date date) {
		SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yy HH:mm");
		return formatter.format(date);
	}

	/**
	 * Validate hex with regular expression
	 * 
	 * @param hex hex for validation
	 * @return true valid hex, false invalid hex
	 */
	public boolean validateUser(final String hex) {
		pattern = Pattern.compile(USER_PATTERN);
		matcher = pattern.matcher(hex);
		return matcher.matches();

	}
}
